/* ver: 0.1, date 05-01-2008 by Marcel Hekman
 * - Implemented: fetchNextLine, decodeElementCode, parseRestOfFile, getMaze
 */

package mazeAssignment;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.StringTokenizer;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileNameExtensionFilter;

/*
 * A utility class used to import maze text files. Used in debugging.
 * 
 * Maze Format:
 * 
 * 0x01	North wall
 * 0x02	East wall
 * 0x04	South wall
 * 0x08	West wall
 * 0x10	Start
 * 0x20	End
 * 
 * +--+--+--+--+
 * |   xS|x    |
 * +  +  +  +  +
 * |  |  |x    |
 * +  +  +  +--+
 * |  |   x  xE|
 * +  +--+--+  +
 * |           |
 * +--+--+--+--+
 * 
 * ##### Example file #####
 * width: 4
 * height: 4
 * 
 *  9 19  9  3
 * 10 10  8  6
 * 10 12  4 35
 * 12  5  5  6
 * ### End example file ###
 */

public class MazeImporter
{
	private static final int NORTH_WALL = 0x01;
	private static final int EAST_WALL = 0x02;
	private static final int SOUTH_WALL = 0x04;
	private static final int WEST_WALL = 0x08;
	private static final int START = 0x10;
	private static final int END = 0x20;
	
	
	private static JFileChooser fileChooser = null;
	private static FileNameExtensionFilter fileFilter = null;
	
	
	
	/**
	 * Used to initialize JFileChooser class and save the instance.
	 * Because this is class initializes slow sometimes.
	 */
	public static void initFileChooser()
	{
		if(fileChooser == null || fileFilter == null)
		{
			fileChooser = new JFileChooser();
			fileFilter = new FileNameExtensionFilter("Maze file", "mzf");
			fileChooser.setFileFilter(fileFilter);
		}
	}
	
	/**
	 * Used to import a maze.
	 * @return The imported maze
	 */
	public static Maze loadMaze(JFrame frame)
	{
		initFileChooser();
		
		int returnVal = fileChooser.showOpenDialog(frame);
	    if(returnVal != JFileChooser.APPROVE_OPTION)
	    {
	    	return null;
	    }
	    
	    try
	    {
	    	BufferedReader mazeFile = new BufferedReader(
	    			new FileReader(
	    					fileChooser.getSelectedFile() ) );
	    	
	    	StringTokenizer line;
	    	
	    	//Maze width
	    	line = new StringTokenizer( fetchNextLine(mazeFile) );
	    	if(line.countTokens() != 2 || !line.nextToken().equals("width:"))
	    		throw new Exception();
	    	
	    	int width = Integer.parseInt(line.nextToken());
	    	
	    	//Maze height
	    	line = new StringTokenizer( fetchNextLine(mazeFile) );
	    	if(line.countTokens() != 2 || !line.nextToken().equals("height:"))
	    		throw new Exception();
	    	
	    	int height = Integer.parseInt(line.nextToken());
	    	
	    	//Generate a maze
	    	Maze maze = new SingleArrayMaze(width, height);
	    	
	    	
	    	//Parse the rest of the maze
	    	parseRestOfFile(maze, mazeFile);
		    
	    	mazeFile.close();
	    	
			return maze;
	    }
	    catch (FileNotFoundException fe)
	    {
			return null;
	    }
		catch(NumberFormatException ne)
		{
			return null;
		}
		catch(NullPointerException ne)
		{
			return null;
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	
	/*
	 * ##### Example file #####
	 * width: 4
	 * height: 4
	 * 
	 *  9 19  9  3
	 * 10 10  8  6
	 * 10 12  4 35
	 * 12  5  5  6
	 * ### End example file ###
	 */
	private static void parseRestOfFile(Maze maze, BufferedReader br) throws Exception
	{
		int mazeWidth = maze.getSizeX();
		int mazeHeight = maze.getSizeY();
		int currentRow = 0;
		
		String lineString = null;
		while(currentRow < mazeHeight && (lineString = fetchNextLine(br)) != null)
		{
			StringTokenizer line = new StringTokenizer(lineString);
			
			if(line.countTokens() != mazeWidth)
				throw new Exception();
			
			int tokes = line.countTokens();
			for(int i = 0; i < tokes; i++)
			{
				int elementCode = Integer.parseInt(line.nextToken());
				
				decodeElementCode( maze.getElement(i, currentRow), elementCode );
			}
			
			currentRow++;
		}
	}
	
	private static void decodeElementCode(MazeElement current, int code)
	{
		int sState = 0;
		
		if((START & code) != 0)
			sState = MazeElement.SOLVE_START;
		else if((END & code) != 0)
			sState = MazeElement.SOLVE_END;
		
		current.setSolveState(sState);
		
		current.setNorth((NORTH_WALL & code) != 0);
		current.setEast((EAST_WALL & code) != 0);
		current.setSouth((SOUTH_WALL & code) != 0);
		current.setWest((WEST_WALL & code) != 0);
	}
	
	private static String fetchNextLine(BufferedReader br) throws IOException
	{
		String returnLine = "";
		
		while(returnLine.equals(""))
		{
			returnLine = br.readLine();
		}
		
		return returnLine;
	}
	
	
	/**
	 * Used to export a maze.
	 */
	public static void saveMaze(JFrame frame, Maze maze)
	{
		initFileChooser();
    	
    	int returnVal = fileChooser.showSaveDialog(frame);
	    if(returnVal != JFileChooser.APPROVE_OPTION)
	    {
	    	return;
	    }
	    
	    try
	    {
	    	BufferedWriter mazeFile = new BufferedWriter(
	    			new FileWriter(
	    					fileChooser.getSelectedFile() ) );
	    	
	    	mazeFile.newLine();
	    	
	    	//Maze width
	    	int width = maze.getSizeX();
	    	mazeFile.write("width: "+width);
	    	mazeFile.newLine();
	    	
	    	//Maze height
	    	int height = maze.getSizeY();
	    	mazeFile.write("height: "+height);
	    	mazeFile.newLine();
	    	mazeFile.newLine();
	    	
	    	//Write the maze
	    	for(int y = 0; y < height; y++)
	    	{
	    		for(int x = 0; x < width; x++)
		    	{
	    			int code = encodeElementCode(maze.getElement(x, y));
	    			
	    			//Add an extra space
	    			if(code < 10)
	    				mazeFile.write(' ');
	    			
	    			mazeFile.write(code+" ");
		    	}
	    		mazeFile.newLine();
	    	}
	    	
		    //Close the buffer
	    	mazeFile.close();
	    }
	    catch (FileNotFoundException fe)
	    { }
		catch(NumberFormatException ne)
		{ }
		catch(NullPointerException ne)
		{ }
		catch(Exception e)
		{ }
	}
	
	private static int encodeElementCode(MazeElement current)
	{
		int code = 0;
		
		if(current.getSolveState() == MazeElement.SOLVE_START)
			code |= START;
		
		if(current.getSolveState() == MazeElement.SOLVE_END)
			code |= END;
		
		if(current.getNorth())
			code |= NORTH_WALL;
		
		if(current.getEast())
			code |= EAST_WALL;
		
		if(current.getSouth())
			code |= SOUTH_WALL;
		
		if(current.getWest())
			code |= WEST_WALL;
		
		return code;
	}
}
